package xworker.gluon.netty;

import javafx.beans.property.SimpleBooleanProperty;
import org.xmeta.ActionContext;
import org.xmeta.Thing;
import org.xmeta.World;
import org.xmeta.cache.ThingCache;
import org.xmeta.codes.XmlCoder;
import org.xml.sax.SAXException;
import xworker.gluon.netty.protocols.GluonMessages;
import xworker.netty.NettyClient;
import xworker.netty.NettyClientListener;
import xworker.netty.NettySession;
import xworker.netty.handlers.message.Handler;
import xworker.lang.executor.Executor;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * Client的NettyClient
 */
public class ClientClient {
    private static final String TAG = ClientClient.class.getName();
    static NettyClient client;
    public static String name;
    public static String providerName;
    public static String providerPassword;
    static final Object lockObj = new Object();
    public static SimpleBooleanProperty applicationConfigStatus = new SimpleBooleanProperty();
    public static SimpleBooleanProperty viewConfigStatus = new SimpleBooleanProperty();
    public static SimpleBooleanProperty layerConfigStatus = new SimpleBooleanProperty();
    public static SimpleBooleanProperty networkStatus = new SimpleBooleanProperty(false);
    public static SimpleBooleanProperty registStatus = new SimpleBooleanProperty(false);

    public static void setName(String name){
        ClientClient.name = name;
    }

    public static void disconnect(){
        if(client != null){
            client.close();
        }
    }

    public static void connect(String host, int port){
        if(client != null){
            client.close();
        }else{
            Thing thing = World.getInstance().getThing("xworker.gluon.netty.ClientClient");
            client = new NettyClient(thing, new ActionContext());
            client.removeListener(client);
            client.addListener(new NettyClientListener() {
                @Override
                public void onConnected(NettyClient nettyClient) {
                    Executor.info(TAG, "Connect to server successed.");
                    if(providerName != null && providerPassword != null){
                        GluonMessages.ConnectProviderReq.Builder builder = GluonMessages.ConnectProviderReq.newBuilder();
                        builder.setClientName(name);
                        builder.setName(providerName);
                        builder.setPassword(providerPassword);

                        nettyClient.sendMessage(builder.build());
                    }
                    networkStatus.set(true);
                }

                @Override
                public void onDisconnected(NettyClient nettyClient) {
                    Executor.info(TAG, "Disconnected from server.");
                    networkStatus.set(false);
                }

                @Override
                public void onBeforeReconnect(NettyClient nettyClient, long waitReconnectTime) {
                    Executor.info(TAG, "Before reconnect to server, watiTime=" + waitReconnectTime);
                }

                @Override
                public void onClosed(NettyClient nettyClient) {
                    Executor.info(TAG, "Client closed!");
                }

                @Override
                public void onConnectSuccess(NettyClient nettyClient) {
                    Executor.info(TAG, "Client connect success.");
                }

                @Override
                public void onConnectFailure(NettyClient nettyClient, Throwable cause) {
                    Executor.info(TAG, "Client connect failure", cause);
                }

                @Override
                public void onConnectCancelled(NettyClient nettyClient) {
                    Executor.info(TAG, "Connect cancelled!");
                }
            });
        }

        client.setHost(host);
        client.setPort(port);
        client.setAutoConnect(true);
        client.connect();
    }

    public static void sendMessage(Object message) throws IOException {
        if(client != null){
            client.sendMessage(message);
        }
    }

    @Handler(requestClass = GluonMessages.ConnectProviderResp.class)
    public static void handleConnectProviderResp(GluonMessages.ConnectProviderResp resp, NettySession session){
        Executor.info(TAG, "Connect to provider response, " + resp);
        if(resp.getResult() == 0){
            registStatus.set(true);
        }else{
            registStatus.set(false);
        }
    }

    @Handler(requestClass = GluonMessages.ClientReceiveThing.class)
    public static void handleClientReceiveThing(GluonMessages.ClientReceiveThing req, NettySession session){
        try {
            Executor.info(TAG, "Receive thing, path=" + req.getPath());
            saveThing(req.getPath(), req.getContent().toByteArray());
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void saveThing(String path, byte[] content) throws ParserConfigurationException, IOException, SAXException {
        //解析和保存模型
        Thing thing = new Thing();
        XmlCoder.parse(thing, new String(content, StandardCharsets.UTF_8));
        thing.saveAs("_local", path);

        //移除缓存
        ThingCache.remove(path);

        //保存到配置
        if(thing.isThingByName("AppManager")){
            addConfig(thing, "Applications@0", "xworker.gluon.netty.GluonConfig/@Applications", "xworker.gluon.netty.GluonConfig/@Applications/@Application");
            //目的是触发改变事件
            applicationConfigStatus.set(!applicationConfigStatus.getValue());
        }else if(thing.isThingByName("View")){
            addConfig(thing, "Views@0", "xworker.gluon.netty.GluonConfig/@Views", "xworker.gluon.netty.GluonConfig/@Views/@Application");
            viewConfigStatus.set(!viewConfigStatus.getValue());
        }else if(thing.isThingByName("Layer")){
            addConfig(thing, "Layers@0", "xworker.gluon.netty.GluonConfig/@Layers", "xworker.gluon.netty.GluonConfig/@Layers/@Application");
            layerConfigStatus.set(!layerConfigStatus.getValue());
        }
    }

    public static void addConfig(Thing thing, String parentPath, String parentDescriptor, String configDescriptor){
        synchronized (lockObj){
            Thing config = getConfigThing();

            Thing apps = config.getThing(parentPath);
            if(apps == null){
                apps = new Thing(parentDescriptor);
                config.addChild(apps);
            }

            boolean have = false;
            for(Thing child : apps.getChilds()){
                if(thing.getMetadata().getPath().equals(child.getString("path"))){
                    have = true;
                    child.getAttributes().putAll(thing.getAttributes());
                    child.set("path", thing.getMetadata().getPath());
                    break;
                }
            }

            if(!have){
                Thing app = new Thing(configDescriptor);
                app.getAttributes().putAll(thing.getAttributes());
                app.set("path", thing.getMetadata().getPath());
                apps.addChild(app);
            }

            config.save();
        }
    }

    public static Thing getConfigThing(){
        synchronized (lockObj){
            String path = "_local.gluon.GluonConfig";
            Thing config = World.getInstance().getThing(path);
            if(config == null){
                config = new Thing("xworker.gluon.netty.GluonConfig");
                Thing apps = new Thing("xworker.gluon.netty.GluonConfig/@Applications");
                config.addChild(apps);
                Thing views = new Thing("xworker.gluon.netty.GluonConfig/@Views");
                Thing homeView = new Thing("xworker.gluon.netty.GluonConfig/@Views/@Application");
                homeView.set("name", "home");
                homeView.set("label", "Home");
                homeView.set("path", "xworker.gluon.xapp.views.HomeView");
                views.addChild(homeView);
                Thing networkView = new Thing("xworker.gluon.netty.GluonConfig/@Views/@Application");
                networkView.set("name", "network");
                networkView.set("label", "Network");
                networkView.set("path", "xworker.gluon.xapp.views.NetworkView");
                views.addChild(networkView);
                config.addChild(views);

                Thing layers = new Thing("xworker.gluon.netty.GluonConfig/@Layers");
                config.addChild(layers);
                config.saveAs("_local", path);
            }

            return config;
        }
    }
}
